21.2 清理
垃圾清理操作在处理span时会检查specials链表,将不可达对象的finalizer函数添加到一个特定待执行队列。
mgcsweep.go
func mSpan_Sweep(s *mspan, preserve bool) bool { specialp := &s.specials special := specialp for special != nil { // 利用偏移量计算出目标对象地址 p := uintptr(s.start<<_PageShift) + uintptr(special.offset)/sizesize // 检查回收标记 // 如果没有标记,那么属于可回收对象,准备执行 finalizer hbits := heapBitsForAddr(p) if !hbits.isMarked() { p := uintptr(s.start<<_PageShift) + uintptr(special.offset) y := special // 调整 span.specials 链表 special = special.next *specialp = special // 释放 special,将 finalizer 放入待执行队列 // 下次回收该对象时,已经没有 finalizer 需要处理了 if !freespecial(y, unsafe.Pointer(p), size, false) { // 重新将目标对象标记,避免被清理 // 为了让 finalizer 正确执行,必须延长目标对象生命周期 hbits.setMarkedNonAtomic() } } else { // object is still live: keep special record specialp = &special.next special = *specialp } } }
mheap.go
func freespecial(s *special, p unsafe.Pointer, size uintptr, freed bool) bool { switch s.kind { case KindSpecialFinalizer: // addfinalizer 创建的原本就是 specialfinalizer,它匿名嵌入 special, // 此处不过是转换回原本样子而已 sf := (*specialfinalizer)(unsafe.Pointer(s)) // 将函数放入待执行队列 queuefinalizer(p, sf.fn, sf.nret, sf.fint, sf.ot) // 释放 specialfinalizer 对象 fixAlloc_Free(&mheap.specialfinalizeralloc, (unsafe.Pointer)(sf)) return false // don’t free p until finalizer is done case _KindSpecialProfile: … default: throw(“bad special kind”) panic(“not reached”) } }
从清理操作对持有finalizer不可达对象的态度可以看出,析构函数会延长对象的生命周期,直到下一次垃圾回收才会真正被清理。其根本理由就是,finalizer函数执行时可能会访问目标对象,比如释放目标对象持有的相关资源等等。
另外,finalizer的执行依赖于垃圾清理操作,我们无法确定其准确执行时间。且不保证在进程退出前,一定会得到执行。因此,不能用finalizer去执行类似flush cache操作。